/**
 * Copyright 2012-2020 The MicroRpc Authors
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.ww.microrpc.rpc;

import com.ww.microrpc.RpcMapping;
import com.ww.microrpc.util.JsonUtil;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.beans.Introspector;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static com.ww.microrpc.rpc.Util.checkState;

/**
 * Defines what annotations and values are valid on interfaces.
 */
public interface Contract {

    /**
     * Called to parse the methods in the class that are linked to HTTP requests.
     *
     * @param targetType {@link MicroRpc.Target#type() type} of the MicroRpc interface.
     */
    List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType);

//    MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method);

    abstract class BaseContract implements Contract {


        /**
         * @deprecated use {@link #parseAndValidateMetadata(Class, Method)} instead.
         */
        @Deprecated
        public MethodMetadata parseAndValidateMetadata(Method method) {
            return parseAndValidateMetadata(method.getDeclaringClass(), method);
        }

        /**
         * Called indirectly by {@link #parseAndValidateMetadata(Class)}.
         */
        public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
            final MethodMetadata data = new MethodMetadata();
            data.targetType(targetType);
            data.method(method);
            data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
            data.configKey(MicroRpc.configKey(targetType, method));


            return data;
        }

    }

    class Default implements Contract {

        @Override
        public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
            checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s",
                    targetType.getSimpleName());
            checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s",
                    targetType.getSimpleName());
            if (targetType.getInterfaces().length == 1) {
                checkState(targetType.getInterfaces()[0].getInterfaces().length == 0,
                        "Only single-level inheritance supported: %s",
                        targetType.getSimpleName());
            }
            final Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
            for (final Method method : targetType.getMethods()) {
                if (method.getDeclaringClass() == Object.class ||
                        (method.getModifiers() & Modifier.STATIC) != 0 ||
                        Util.isDefault(method)) {
                    continue;
                }
                final MethodMetadata metadata = parseAndValidateMetadata(targetType, method);
                checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s",
                        metadata.configKey());
                result.put(metadata.configKey(), metadata);
            }
            return new ArrayList<>(result.values());
        }

        public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
            final MethodMetadata methodMetadata = new MethodMetadata();
            methodMetadata.targetType(targetType);
            methodMetadata.method(method);
            methodMetadata.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
            methodMetadata.configKey(MicroRpc.configKey(targetType, method));
            methodMetadata.className(getDefaultBeanName(targetType.getName()));
            methodMetadata.methodName(method.getName());
            if (method.isAnnotationPresent(RpcMapping.class)) {
                RpcMapping annotation = method.getAnnotation(RpcMapping.class);
                if (!StringUtils.hasText(annotation.targetMethod())) {
                    throw new IllegalStateException("'url' must be provided in @" + RpcMapping.class.getSimpleName());
                }
                methodMetadata.methodName(annotation.targetMethod());

                String targetClass = annotation.targetClass();
                if (StringUtils.hasText(targetClass)) {
                    methodMetadata.className(targetClass);
                }
            }
            return methodMetadata;
        }

        private String getDefaultBeanName(String className) {
            Assert.state(className != null, "no Bean class name set");
            String shortName = ClassUtils.getShortName(className);
            return Introspector.decapitalize(shortName);
        }
    }
}
